---
title: 接口
description: 接口用来描述类应该做什么，而不指定它们具体该如何做。
---
接口用来描述类应该做什么，而不指定它们具体该如何做。一个类可以实现（implement）一个或多个接口。只要符合所要求的接口，就可以使用实现了这个接口的类（即实现类）的对象。

Java 不支持多继承，其主要原因是多重继承会让语言变得非常复杂，或者效率会降低。 接口可以提供多重继承的大多数好处，同时还能避免多重继承的复杂性和低效性。

## 接口的概念
接口不是类，而是对希望符合这个接口的类的一组需求。

例如 Arrays 类中的 sort 方法承诺可以对对象数组进行排序，但要求满足下面的条件：对象所属的类必须实现 Comparable 接口。
```java title='Comparable接口的代码'
public interface Comparable<T>{
    int compareTo(T other);
}
```
在这个接口中，compareTo 方法是抽象的，它没有具体实现。任何实现 Comparable 接口的类都需要包含一个 compareTo 方法。否则，这个类也应该是抽象的。

接口中的所有方法都自动是 public 方法。因此，在接口中声明方法时，不必提供关键字 public，但在实现接口时，必须加上关键字 public，否则，编译器会认为这个方法的访问属性是包可访问。

这个特定接口只有一个方法，而有的接口可能包含多个方法。接口还可以定义常量。但接口绝不可能有实例字段 ，在 Java 8 之前，接口中的方法都是抽象方法。

现在，假设希望使用 Arrays 类的 sort 方法对 Employee 都对象数组进行排序，Employee 类就必须实现 Comparable 接口。

让类实现一个接口，需要完成下面两个步骤：

1. 将类声明为实现给定的接口。
2. 对接口中的所有方法提供定义。

要声明一个类实现某个接口，需要使用关键字 implements ：
```java
class Employee implements Comparable
```
当然，现在 Employee 类需要提供 compareTo 方法。假设我们希望根据员工的薪水进行比较。以下是 compareTo 方法的一个实现：
```java
public int compareTo(Object otherObject){
    Employee other = (Employee) otherObjcet;
    return Double.compare(salary,other.salary);
}
```

:::tip
Comparable 接口的文档建议 compareTo 方法应当与 equals 方法兼容。
:::

如果让一个类使用排序服务必须让它实现 CompareTo 方法，因为要向 sort 方法提供对象的比较方式。但是为什么不能在 Employee 类中直接提供一个 compareTo 方法（而不实现 Comparable 接口）呢？

使用接口的主要原因在于：Java 程序设计语言是一种强类型语言，调用方法时，编译器要能检查这个方法确实存在。

```java title='Employee.java'

/**
 * @author 王开琦
 */
public class EmployeeSortTest {
    public static void main(String[] args) {
        var staff = new Employee[3];
        staff[0] = new Employee("wkt", 45000);
        staff[1] = new Employee("zbb", 35000);
        staff[2] = new Employee("wkq", 55000);

        Arrays.sort(staff);
        for (Employee employee : staff) {
            System.out.println("employee.toString() = " + employee.toString());
        }
    }
}

```

```java title='EmployeeSortTest.java'
package pub.wkq.java.interfaces;

/**
 * @author 王开琦
 */
public class Employee implements Comparable<Employee> {

    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
    @Override
    public int compareTo(Employee o) {
        return Double.compare(salary, o.salary);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
```
## 接口的属性
接口不是类。具体来说，不能使用 new 操作符实例化一个接口。

尽管接口不能实例化，但可以声明接口变量。接口变量必须引用实现了这个接口的一个类对象。
```java
Comparable x  =  new Employee();
```
接下来，如同使用 instanceof 检查一个对象是否属于某个特定类一样，也可以使用 instanceof 检查一个对象是否实现了某个特定的接口：

```java
if(anObject instanceof Comparable){
    ...
}
```
与建立类的继承层次结构一样，也可以扩展接口。这里允许有多条接口链，从通用性较高的接口扩展到专用性较高的接口。
```java title='假设有一个名为 Miveable 的接口'
public interface Moveable {
    void move(double x,double y)
}
```
```java title='然后，可以假设一个名为 Powered 的接口扩展了以上的 Moveable 接口'
public interface Powered extends Moveable{
    double milesPerGallon();
    double SPEED_LIMIT = 95;//a public static final constant
}
```
接口中的方法都自动为 public，类似地，接口中的字段总是 public static final。

尽管每个类只能有一个超类，但可以实现多个接口。这就为定义类的行为提供了极大的灵活性。

例如,Java 中有一个非常重要的内置接口，名为 Cloneable 。如果类实现了这个 Cloneable 接口，Object 类中的 clone 方法就可以创建类对象的一个完全副本。如果希望自己设计的类既能克隆又能比较，只要实现这两个接口就可以了。
```java
class Employee implements Cloneable,Comparable
```
## 静态和私有方法
在 Java 8 中，允许在接口中增加静态方法。理论上讲，没有任何理由认为这是不合法的。只是似乎有违于将接口作为抽象规范的初衷。

目前为止，通常的做法都是将静态方法放在伴随类中。在标准库中，有成对出现的接口类和实用工具类，如 Collection/Collections 或 Path/Paths.

可以由一个 URI 或者字符串序列构造一个文件或目录的路径，如 Paths.get("jdk17","conf","security") 。在 Java 11 中， Path 接口提供了等价的方法：

```java
public interface Path{
    public static Path of(URI uri){...}
    public static Path of(String first,String... more){...}
}
```
这样一来，Paths 类就不是必须的了。

在 Java 9 中，接口中的方法可以是 private 方法。 private 方法可以是静态方法或实例方法。由于。由于私有方法只能在接口本身的方法中使用，所以它们的用途有限，只是作为接口中其它方法的辅助方法。
## 默认方法
可以任何接口方法提供一个默认实现。必须用 default 修饰符标记这样一个方法。
```java
public interface Comparable<T>{
    default int compareTo(T other){return 0}
}
```
Comparable 的每一个具体实现都会覆盖这个方法。不过有些情况下，默认方法可能会很有用。

例如，Iterator 接口，用于访问一个数据结构中的元素。这个接口声明了一个 remove 方法。
```java
public interface Iterator<T>{
    boolean hasNext();
    E next();
    default void remove(){
        throw new UnsupportedOperationException("remove")
    }
}
```
如果你要实现一个迭代器，就需要提供 hasNext 和 next 方法。这些方法没有默认实现————它们要依赖于你要遍历访问的数据结构。不过，如果迭代器是只读的，就不用操心实现 remove 方法。

默认方法可以调用其它方法。例如，Collection 接口可以定义一个便利方法：
```java
public interface Collection{
    int size();
    default boolean isEmpty();
}
```

默认方法的一个重要用法是接口演化，为接口增加新方法，实现它的类将不能编译，因为它没有实现这个新方法。将方法实现为一个默认（default）方法就可以解决这个问题。
## 解决默认方法冲突
如果在一个接口中将一个方法定义为默认方法，然后又在超类或另一个接口中定义了同样的方法，会发生什么情况。
1. 超类优先。如果超类提供了一个具体方法，同名而且有相同参数类型的默认方法会被忽略。
2. 接口冲突。如果一个接口提供了一个默认方法，另一个接口提供了一个同名而且参数类型相同的方法（不管是否是默认方法），必须覆盖这个方法来解决冲突。



## Comparator 接口

对一个对象数组进行排序，前提条件是这些对象实现了 Comparable 接口的类的实例。

例如，可以对一个字符串进行排序，因为 String 类实现了 `Comparable<String>`,而且 String.compareTo 方法可以按照字典顺序比较字符串。
现在假设我们希望按照长度递增的顺序对字符串进行排序，而不是按照字典顺序进行排序，肯定不能让 String类用两种不同的方式实现 compareTo 方法，更何况，String类也无法修改。

要处理这种情况，Arrays.sorts 方法还有第二个版本，接受一个数组和一个比较器（comparator）作为参数，比较器是实现了 Comparator 接口的类的实例。

```java title='Comparator接口'
public interface Comparator<T>{
    int compare(T first,T second);
}
```

```java title='要按长度比较字符串，可以定义一个实现 Comparator<String> 的类'
package pub.wkq.interfaces;

import java.util.Comparator;

/**
 * @author 王开琦
 */
public class StringLengthComparator implements Comparator<String> {

    @Override
    public int compare(String first, String second) {
        return first.length() - second.length();
    }
}
```

```java title='具体完成比较时，需要创建一个比较器实例'
package pub.wkq.interfaces;

/**
 * @author 王开琦
 */
public class Main {
    public static void main(String[] args) {
        var comparator = new StringLengthComparator();
        if (comparator.compare("hello", "world") > 0) {

        }
    }
}
```
这个 compare 方法要在比较器对象上调用，而不是在字符串本身。尽管 StringLengthComparator 对象没有状态，不过还是需要创建一个对象实例。然后用这个实例来调用 compare 方法————它不是一个静态方法。

要对一个数组排序，需要为 Arrays.sort 方法传入一个 StringLengthComparator 对象。

## 对象克隆
Cloneable 接口表示一个类提供了一个安全的 clone 方法。

一个包含对象引用的变量建立副本时会发生什么（拷贝）。原变量和副本变量都是同一个对象的引用。这说明，任何一个变量的改变都会影响另一个变量。

![](https://img.wkq.pub/hexo/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-02-14%20204053.png)

```java title='拷贝'
var original = new Employee("wkq", 30000);
var copy = original;

copy.raiseSalary(10);//也会改变 original
```

如果希望 copy 一个新对象，它的初始状态与 original 相同，但是之后它们的状态可能不同，这种情况下就要使用 clone 方法。

```java title='克隆'
Employee copy = original.clone();
copy.raiseSalary(10);//original 不会改变
```

不过并没有这么简单。clone 方法是 Object 的一个 protected 方法，这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象。这个限制是有原因的。

Object 类的克隆方法由于对子类一无所知，所以只能逐个字段地进行拷贝。如果对象中的所有实例字段都是基本数据类型，拷贝这些字段没有任何问题。但是如果对象包含子对象的引用，拷贝字段就会得到相同子对象的另一个引用，这样一来，源对象和克隆对象依然会共享一些信息。

默认的克隆操作是浅拷贝。如果源对象和浅克隆对象共享的子对象是不可变的，那么这种共享是安全的。

但通常子对象都是可变的，所以必须重新定义 clone 方法来建立一个深拷贝。这会克隆所有子对象，

对于每一个类，需要确认：

1. 默认的 clone 方法就能满足要求。
2. 可以在可变的子对象上调用 clone 来弥补默认的 clone 方法。
3. 不该使用 clone。

实际上第三项是默认选项。如果选择第一项或第二项，类必须：

1. 实现 Cloneable 接口。
2. 重新定义 clone 方法，并指定 public 访问修饰符。

 

Cloneable 接口并没有 clone 方法，这个方法是从 Object 类继承的。这个接口只是作为一个标记，指示类设计者了解克隆过程。如果一个对象请求克隆，但是没有实现这个方法，就会生成一个检查型异常。

:::tip

Cloneable 接口是 Java 提供的少数标记接口（tagging interface）之一。Comparable 等接口的通常用途是确保一个类实现一个特定的方法或一组方法。标记接口不含任何方法，它唯一的作用就是允许在类型检查中使用 instanceof。

:::

即使 clone 的默认（浅拷贝）实现能够满足要求，还是需要实现 Cloneable 接口，将 clone 重新定义为 public ,再调用 super.clone()。

```java
public class Employee implements Cloneable{
	//public access,change return type
    @Override
    protected Employee clone() throws CloneNotSupportedException {
        return (Employee) super.clone();
    }
}    
```

这里只是让这个方法是公共的。要建立浅拷贝，还需要做更多的工作，克隆对象中可变的实例字段。

```java title=‘创建深拷贝的clone方法的一个例子’
@Override
protected Employee clone() throws CloneNotSupportedException {
    Employee cloned = (Employee) super.clone();
    //clone mutable fields
    cloned.hireData = (Date) hireData.clone();
    return cloned;
}
```

Object 类的 clone 方法可能会抛出一个 ClassNotSupportedException ，如果在一个对象上调用 clone，但这个对象的类并没有实现 Cloneable 接口，就会发生这种情况。

